import { MathUtil } from './MathUtil';
import { Vector3 } from './Vector3';

/**
 * 三维线段。
 */
export class Line3 {
    /**方向 */
    public dir: Vector3;

    public from: Vector3;

    public to: Vector3;

    /**
     * 构造函数。
     * 
     * @param from 起点
     * @param to 终点
     */
    public constructor(from?: Vector3, to?: Vector3) {
        this.from = new Vector3();
        this.to = new Vector3();
        if (from && to)
            this.dir = this.direction();
    }

    /**
     * 判断是否有效。
     * 
     */
    public isValidate(): boolean {
        if (this.from) {
            return false;
        }

        if (this.dir) {
            return false;
        }

        return true;
    }

    /**
     * 设置端点
     * 
     */
    public set(from: Vector3, to: Vector3): Line3 {
        if (from && to)
            this.dir = this.direction();
        return this;
    }

    /**
     * 获得距离平方。
     *
     * @return 距离平方
     */
    public distanceSq(): number {
        return this.from.distanceToSquared(this.to);
    }

    /**
     * 获得距离。
     *
     * @return 距离
     */
    public distance(): number {
        return this.from.distanceTo(this.to);
    }

    /**
     * 获得中心点。
     *
     * @param target 目标
     * @return 中心点
     */
    public center(target?: Vector3): Vector3 {
        var result = target || new Vector3();
        return result.addVectors(this.from, this.to).multiplyScalar(0.5);
    }

    /**
     * 获得方向。
     *
     * @param target 目标
     * @return 中心点
     */
    public direction(target?: Vector3): Vector3 {
        var result = target || new Vector3();
        result.assign(this.to);
        result.sub(this.from);
        result.normalize();
        return result;
    }

    /**
     * 判断是否相等。
     *
     * @param line 线
     * @return 是否相等
     */
    public equals(line: Line3): boolean {
        return line.from.equals(this.from) && line.to.equals(this.to);
    }

    /**
    * 判断是否近似相等。
     *
     * @param value 内容
     * @param tolerance 误差
     * @return 是否相等
    */
    public nearlyEquals(value: Line3, tolerance: number = MathUtil.TOLERANCE): boolean {
        if (this.from.nearlyEquals(value.from, tolerance) && this.to.nearlyEquals(value.to, tolerance)) {
            return true;
        }
        if (this.from.nearlyEquals(value.to, tolerance) && this.to.nearlyEquals(value.from, tolerance)) {
            return true;
        }
        return false;
    }

    /**
     * 复制数据。
     *
     * @return 对象
     */
    public assign(line: Line3): Line3 {
        this.from.assign(line.from);
        this.to.assign(line.to);
        return this;
    }

    public delta(target?: Vector3): Vector3 {
        var result = target || new Vector3();
        return result.subVectors(this.to, this.from);
    }

    public at(rate: number, target?: Vector3): Vector3 {
        var result = target || new Vector3();
        return this.delta(result).multiplyScalar(rate).add(this.from);
    }

    /**
     * 复制数据。
     *
     * @return 对象
     */
    public copy(line: Line3): Line3 {
        return this.assign(line);
    }

    /**
     * 克隆对象。
     *
     * @return 对象
     */
    public clone(): Line3 {
        var result = new (this as any).constructor();
        result.assign(this);
        return result;
    }

    /**
      * 获得目标点与直线最近的点。
      * @param point 点
      * @return 直线上与目标点最近的点
      */
    public getClosestPoint(des: Vector3): Vector3 {
        var direction = this.dir.clone();
        var dt = new Vector3(des.x - this.from.x, des.y - this.from.y, des.z - this.from.z);
        var t = dt.dot(direction);
        var res = direction.multiplyScalar(t);
        res.add(this.from);

        return res;
    }

    /**
     * 计算两条直线最近点。
     * 
     * @param line 直线
     * @return 如果平行返回null,否则返回返回两条直线上离得最近的两个点。
     */
    public getClosestPoints(line: Line3) {
        var p1 = this.from.clone() as Vector3;
        var p2 = line.from.clone() as Vector3;
        var d1 = this.dir.clone();
        var d2 = line.dir.clone();

        var u = new Vector3(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
        var t = d1.dot(d2);

        if (MathUtil.nearlyGreater(Math.abs(t), 1)) {
            // 两条线近似平行
            return null;
        }
        t = (u.dot(d1) - t * u.dot(d2)) / (1 - t * t);
        var m = d1.multiplyScalar(t);
        var ptOnLine1 = m.add(p1);
        var ptOnLine2 = line.getClosestPoint(ptOnLine1);

        return { ptOnLine1: ptOnLine1, ptOnLine2: ptOnLine2 };
    }
}
